#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>

#include <psp2/io/fcntl.h>

#include "activity/activities.h"
#include "setting/setting.h"
#include "gui/gui.h"
#include "emu/emu.h"
#include "utils.h"
#include "file.h"
#include "config.h"

GUI_Texture *Emu_GetStateScreenshotTexture(int num)
{
    GUI_Texture *texture = NULL;
    char path[MAX_PATH_LENGTH];
    EmuStateHeader header;
    int fd = -1;

    MakeSavestateMetaPath(path, num);
    fd = sceIoOpen(path, SCE_O_RDONLY, 0);
    if (fd < 0)
        goto FAILED;

    memset(&header, 0, sizeof(EmuStateHeader));
    if (sceIoRead(fd, &header, sizeof(EmuStateHeader)) != sizeof(EmuStateHeader))
        goto FAILED;

    if (header.version != SAVESTATE_VERSION)
        goto FAILED;

    texture = GUI_CreateTextureFormat(header.screenshot_width, header.screenshot_height, SCEEENSHOT_PIXEL_FORMAT);
    if (texture)
    {
        sceIoLseek(fd, header.screenshot_offset, SCE_SEEK_SET);
        ReadFileDesc(fd, vita2d_texture_get_datap(texture), header.screenshot_size);
    }

    sceIoClose(fd);

    return texture;

FAILED:
    if (fd >= 0)
        sceIoClose(fd);
    if (texture)
        GUI_DestroyTexture(texture);
    return NULL;
}

GUI_Texture *Emu_GetStatePreviewTexture(int num)
{
    GUI_Texture *texture = NULL;
    char path[MAX_PATH_LENGTH];
    EmuStateHeader header;
    int fd = -1;

    MakeSavestateMetaPath(path, num);
    fd = sceIoOpen(path, SCE_O_RDONLY, 0);
    if (fd < 0)
        goto FAILED;

    memset(&header, 0, sizeof(EmuStateHeader));
    if (sceIoRead(fd, &header, sizeof(EmuStateHeader)) != sizeof(EmuStateHeader))
        goto FAILED;

    if (header.version != SAVESTATE_VERSION)
        goto FAILED;

    texture = GUI_CreateTextureFormat(header.preview_width, header.preview_height, SCEEENSHOT_PIXEL_FORMAT);
    if (texture)
    {
        sceIoLseek(fd, header.preview_offset, SCE_SEEK_SET);
        ReadFileDesc(fd, vita2d_texture_get_datap(texture), header.preview_size);
    }

    sceIoClose(fd);

    return texture;

FAILED:
    if (fd >= 0)
        sceIoClose(fd);
    if (texture)
        GUI_DestroyTexture(texture);
    return NULL;
}

int Emu_GetStateDiskImageIndex(int num)
{
    char path[MAX_PATH_LENGTH];
    EmuStateHeader header;
    int fd = -1;

    MakeSavestateMetaPath(path, num);
    fd = sceIoOpen(path, SCE_O_RDONLY, 0);
    if (fd < 0)
        goto FAILED;

    memset(&header, 0, sizeof(EmuStateHeader));
    if (sceIoRead(fd, &header, sizeof(EmuStateHeader)) != sizeof(EmuStateHeader))
        goto FAILED;

    if (header.version != SAVESTATE_VERSION)
        goto FAILED;

    sceIoClose(fd);

    return header.disk_index;

FAILED:
    if (fd >= 0)
        sceIoClose(fd);
    return 0;
}

int Emu_LoadState(int num)
{
    int ret = 0;
    char path[MAX_PATH_LENGTH];
    void *state_buf = NULL;
    uint32_t state_size = 0;
    int fd = -1;

    uint32_t serialize_size = retro_serialize_size();
    if (serialize_size == 0)
    {
        APP_LOG("[LOADSTATE] retro_serialize_size failed!\n");
        return -1;
    }

    MakeSavestateDataPath(path, num);
    fd = sceIoOpen(path, SCE_O_RDONLY, 0);
    if (fd < 0)
    {
        APP_LOG("[LOADSTATE] open state file failed!\n");
        goto FAILED;
    }

    state_size = sceIoLseek(fd, 0, SCE_SEEK_END);
    if (state_size != serialize_size)
    {
        APP_LOG("[LOADSTATE] state size is not match %u ==> %u!\n", state_size, serialize_size);
        goto FAILED2;
    }

    sceIoLseek(fd, 0, SCE_SEEK_SET);
    if (AllocateReadFileDesc(fd, &state_buf, state_size) != state_size)
    {
        APP_LOG("[LOADSTATE] read state file failed!\n");
        goto FAILED;
    }

    if (!retro_unserialize(state_buf, state_size))
    {
        APP_LOG("[LOADSTATE] retro_unserialize failed!\n");
        goto FAILED;
    }

    int disk_index = Emu_GetStateDiskImageIndex(num);
    if (disk_index != Emu_DiskGetImageIndex())
        Emu_DiskChangeImageIndex(disk_index);

    Emu_CleanAudio();

END:
    if (state_buf)
        free(state_buf);
    if (fd >= 0)
        sceIoClose(fd);
    return ret;

FAILED:
    ret = -1;
    goto END;

FAILED2:
    ret = -2;
    goto END;
}

int Emu_SaveState(int num)
{
    int ret = 0;
    char path[MAX_PATH_LENGTH];
    void *screenshot_buf = NULL;
    void *preview_buf = NULL;
    void *state_buf = NULL;
    uint32_t screenshot_width;
    uint32_t screenshot_height;
    uint32_t screenshot_size;
    uint32_t preview_width;
    uint32_t preview_height;
    uint32_t preview_size;
    uint32_t state_size;
    int fd = -1;

    // Save state data
    state_size = retro_serialize_size();
    if (state_size == 0)
    {
        APP_LOG("[SAVESTATE] retro_serialize_size failed!\n");
        return -2;
    }

    state_buf = malloc(state_size);
    if (!state_buf)
    {
        APP_LOG("[SAVESTATE] alloc state buf failed!\n");
        goto FAILED;
    }

    if (!retro_serialize(state_buf, state_size))
    {
        APP_LOG("[SAVESTATE] retro_serialize failed!\n");
        goto FAILED;
    }

    MakeSavestateDir(path);
    CreateFolder(path);

    MakeSavestateDataPath(path, num);
    if (WriteFile(path, state_buf, state_size) != state_size)
    {
        sceIoRemove(path);
        APP_LOG("[SAVESTATE] write state data failed\n");
        goto FAILED;
    }

    // Save state meta
    uint32_t base_width, base_height;
    Emu_GetVideoBaseDimensions(&base_width, &base_height);

    int rotate_cw = Emu_GetVideoDisplayRotateCW();
    if (rotate_cw == TYPE_DISPLAY_ROTATE_CW_90 || rotate_cw == TYPE_DISPLAY_ROTATE_CW_270)
    {
        screenshot_width = base_height;
        screenshot_height = base_width;
    }
    else
    {
        screenshot_width = base_width;
        screenshot_height = base_height;
    }

    screenshot_size = 0;
    screenshot_buf = Emu_GetVideoScreenshotData(&screenshot_width, &screenshot_height, &screenshot_size, rotate_cw);
    if (!screenshot_buf)
    {
        APP_LOG("[SAVESTATE] make screenshot failed!\n");
        goto FAILED;
    }

    preview_width = 0;
    preview_height = 0;
    Setting_GetStatePreviewSize(&preview_width, &preview_height);
    preview_size = 0;
    preview_buf = Emu_GetVideoScreenshotData(&preview_width, &preview_height, &preview_size, rotate_cw);
    if (!preview_buf)
    {
        APP_LOG("[SAVESTATE] make preview failed!\n");
        goto FAILED;
    }

    EmuStateHeader header;
    memset(&header, 0, sizeof(EmuStateHeader));
    header.version = SAVESTATE_VERSION;
    header.screenshot_width = screenshot_width;
    header.screenshot_height = screenshot_height;
    header.preview_width = preview_width;
    header.preview_height = preview_height;
    header.disk_index = Emu_DiskGetImageIndex();
    header.screenshot_offset = sizeof(EmuStateHeader);
    header.screenshot_size = screenshot_size;
    header.preview_offset = header.screenshot_offset + screenshot_size;
    header.preview_size = preview_size;

    MakeSavestateMetaPath(path, num);
    fd = sceIoOpen(path, SCE_O_WRONLY | SCE_O_CREAT | SCE_O_TRUNC, 0777);
    if (fd < 0)
        goto FAILED_WRITE_META;

    if (WriteFileDesc(fd, &header, sizeof(EmuStateHeader)) != sizeof(EmuStateHeader))
        goto FAILED_WRITE_META;

    if (WriteFileDesc(fd, screenshot_buf, screenshot_size) != screenshot_size)
        goto FAILED_WRITE_META;

    if (WriteFileDesc(fd, preview_buf, preview_size) != preview_size)
        goto FAILED_WRITE_META;

END:
    if (screenshot_buf)
        free(screenshot_buf);
    if (preview_buf)
        free(preview_buf);
    if (state_buf)
        free(state_buf);
    if (fd >= 0)
        sceIoClose(fd);
    return ret;

FAILED:
    ret = -1;
    goto END;

FAILED_WRITE_META:
    APP_LOG("[SAVESTATE] write state meta failed\n");
    sceIoRemove(path);
    goto FAILED;
}

int Emu_DeleteState(int num)
{
    char path[MAX_PATH_LENGTH];
    MakeSavestateMetaPath(path, num);
    sceIoRemove(path);
    MakeSavestateDataPath(path, num);
    sceIoRemove(path);
    return 0;
}